﻿
//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/********************************************************
 * ADO.NET 2.0 Data Provider for SQLite Version 3.X
 * Written by Robert Simpson (robert@blackcastlesoft.com)
 * 
 ********************************************************/


using System;
using System.Data;
using System.Data.Common;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.IO;
using EffiProz.Core;
using EffiProz.Core.Results;

namespace System.Data.EffiProz
{
    public sealed partial class EfzConnection : DbConnection, ICloneable
    {

        /// <summary>
        /// State of the current connection
        /// </summary>
        private ConnectionState _connectionState;
        /// <summary>
        /// The connection string
        /// </summary>
        /// 
        private string _connectionString;

        private EfzConnectionOptions _connectionOptions;
           
                           

        internal long Version{ get; set;}

        /**
        * Is this an internal connection?
        */
        internal bool isInternal = false;
        
     
        int _poolVersion;

     

        /// <summary>
        /// This event is raised whenever the database is opened or closed.
        /// </summary>
        public override event StateChangeEventHandler StateChange;


        internal EfzConnectionProxy InnerConnection
        {
            get;
            set;
        }

        public bool IsNetConnection
        {
            get
            {
                return !(_connectionOptions.ConnectionType == DatabaseURL.S_MEM
                    || _connectionOptions.ConnectionType == DatabaseURL.S_FILE);
            }
        }

        public IsolationLevel IsolationLevel
        {
            get
            {
                return _connectionOptions.IsolationLevel;
            }

            set
            {
                _connectionOptions.IsolationLevel = value;
                InnerConnection.SetIsolationLevel(value);
            }
        }



        public bool ReadOnly
        {
            get
            {
                return _connectionOptions.Readonly;
            }

            set
            {
                _connectionOptions.Readonly = value;
                InnerConnection.SetReadOnly(value);
            }
        }

        public bool AutoCommit
        {
            get
            {
                return _connectionOptions.AutoCommit;
            }

            set
            {
                _connectionOptions.AutoCommit = value;
                InnerConnection.SetAutoCommit(value);
            }
        }

        internal bool IsClosed
        {
            get
            {
                return _connectionState == ConnectionState.Closed;
            }
        }
             

        ///<overloads>
        /// Constructs a new EffiProzConnection object
        /// </overloads>
        /// <summary>
        /// Default constructor
        /// </summary>
        public EfzConnection()
            : this(String.Empty)
        {
        }

        /// <summary>
        /// Initializes the connection with the specified connection string
        /// </summary>
        /// <param name="connectionString">The connection string to use on the connection</param>
        public EfzConnection(string connectionString)
        {
            _connectionState = ConnectionState.Closed;
            _connectionOptions = new EfzConnectionOptions();
            _connectionString = String.Empty;

            if ( !String.IsNullOrEmpty(connectionString))
                ConnectionString = connectionString;
        }

        /// <summary>
        /// Clones the settings and connection string from an existing connection.  If the existing connection is already open, this
        /// function will open its own connection, enumerate any attached databases of the original connection, and automatically
        /// attach to them.
        /// </summary>
        /// <param name="connection"></param>
        public EfzConnection(EfzConnection connection)
            : this(connection.ConnectionString)
        {
            if (connection.State == ConnectionState.Open)
            {
                Open();
            }
        }

        /// <summary>
        /// Clears the connection pool associated with the connection.  Any other active connections using the same database 
        /// will be discarded instead of returned to the pool when they are closed.
        /// </summary>
        /// <param name="connection"></param>
        public static void ClearPool(EfzConnection connection)
        {
            EfzConnectionPool.ClearPool(connection.ConnectionString);
           
        }

        /// <summary>
        /// Clears all connection pools.  Any active connections will be discarded instead of sent to the pool when they are closed.
        /// </summary>
        public static void ClearAllPools()
        {
            EfzConnectionPool.ClearAllPools();
        }
    

        /// <summary>
        /// Disposes of the EffiProzConnection, closing it if it is active.
        /// </summary>
        /// <param name="disposing">True if the connection is being explicitly closed.</param>
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            if (disposing)
            {
                Close();

                _connectionString = String.Empty;
                _connectionOptions = null;
            }
        }



        /// <summary>
        /// Raises the state change event when the state of the connection changes
        /// </summary>
        /// <param name="newState">The new state.  If it is different from the previous state, an event is raised.</param>
        internal void OnStateChange(ConnectionState newState)
        {
            ConnectionState oldState = _connectionState;
            _connectionState = newState;

            if (StateChange != null && oldState != newState)
            {
                StateChangeEventArgs e = new StateChangeEventArgs(oldState, newState);
                StateChange(this, e);
            }
        }

       
        /// <summary>
        /// Creates a new EffiProzDBTransaction if one isn't already active on the connection.
        /// </summary>
        /// <param name="isolationLevel">Supported isolation levels are Serializable, ReadCommitted and Unspecified.</param>
        /// <remarks>
        /// Unspecified will use the default isolation level specified in the connection string.  If no isolation level is specified in the 
        /// connection string, Serializable is used.
        /// Serializable transactions are the default.  In this mode, the engine gets an immediate lock on the database, and no other threads
        /// may begin a transaction.  Other threads may read from the database, but not write.
        /// With a ReadCommitted isolation level, locks are deferred and elevated as needed.  It is possible for multiple threads to start
        /// a transaction in ReadCommitted mode, but if a thread attempts to commit a transaction while another thread
        /// has a ReadCommitted lock, it may timeout or cause a deadlock on both threads until both threads' CommandTimeout's are reached.
        /// </remarks>
        /// <returns>Returns a EffiProzDBTransaction object.</returns>
        public new EfzTransaction BeginTransaction(IsolationLevel isolationLevel)
        {           
            return BeginTransaction(isolationLevel,false);
        }

        /// <summary>
        /// Creates a new EffiProzDBTransaction if one isn't already active on the connection.
        /// </summary>
        /// <returns>Returns a EffiProzDBTransaction object.</returns>
        public new EfzTransaction BeginTransaction()
        {            
            return BeginTransaction(_connectionOptions.IsolationLevel,false);
        }

        public EfzTransaction BeginTransaction(IsolationLevel isolationLevel,bool readOnly)
        {
            if (_connectionState != ConnectionState.Open)
                throw new InvalidOperationException("Connection is not open.");

            if (isolationLevel == IsolationLevel.Unspecified) isolationLevel = _connectionOptions.IsolationLevel;

            if (isolationLevel == IsolationLevel.Snapshot || isolationLevel == IsolationLevel.Chaos)
                throw new ArgumentException("Invalid IsolationLevel");

            return InnerConnection.BeginTransaction(isolationLevel, readOnly);
        }

        /// <summary>
        /// Forwards to the local BeginTransaction() function
        /// </summary>
        /// <param name="isolationLevel">Supported isolation levels are Unspecified, Serializable, and ReadCommitted</param>
        /// <returns></returns>
        protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel)
        {           
            return BeginTransaction(isolationLevel,false);
        }

        /// <summary>
        /// Not implemented
        /// </summary>
        /// <param name="databaseName"></param>
        public override void ChangeDatabase(string databaseName)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// When the database connection is closed, all commands linked to this connection are automatically reset.
        /// </summary>
        public override void Close()
        {
            if (State == ConnectionState.Closed || InnerConnection == null) return;

            if (InnerConnection.IsEnlisted)
            {
                EfzConnection cnn = new EfzConnection();

                cnn.InnerConnection = InnerConnection;
                cnn.InnerConnection.Owner = cnn;

                cnn.ConnectionString = ConnectionString;
                cnn._connectionOptions = _connectionOptions;
                cnn.Version = Version;

                cnn._connectionState = _connectionState;            
                              
                cnn.InnerConnection.Enlistment.dbTransaction._cnn = cnn;
                cnn.InnerConnection.Enlistment.DisposeConnection = true;
               
                InnerConnection = null;
            }

            try
            {
                if (InnerConnection != null)
                {
                    lock (InnerConnection)
                    {
                        InnerConnection.DisposeTransaction();
                        InnerConnection.Owner = null;

                        if (_connectionOptions.Pooling)
                        {
                            EfzConnectionPool.Add(_connectionString, InnerConnection, _poolVersion);
                        }
                        else
                        {                          
                             InnerConnection.Dispose();
                        }                       
                    }
                }

                InnerConnection = null;
               
            }
            catch (Exception)
            {
            }
            finally
            {
                OnStateChange(ConnectionState.Closed);
            }
        }


        /// <summary>
        /// Manual distributed transaction enlistment support
        /// </summary>
        /// <param name="transaction">The distributed transaction to enlist in</param>
        public override void EnlistTransaction(System.Transactions.Transaction transaction)
        {
            if (State != ConnectionState.Open)
                throw new InvalidOperationException("Connection is not open");

            InnerConnection.EnlistTransaction(transaction);
        }

       
     
        public override string ConnectionString
        {
            get
            {
                return _connectionString;
            }
            set
            {
                if (value == null)
                    throw new ArgumentNullException();
                else if (_connectionState != ConnectionState.Closed)
                    throw new InvalidOperationException();
                            
                _connectionOptions.ParseConnectionString(value);
                _connectionOptions.Validate();
                _connectionString = value;
            }
        }

        /// <summary>
        /// Create a new EffiProzCommand and associate it with this connection.
        /// </summary>
        /// <returns>Returns an instantiated EffiProzCommand object already assigned to this connection.</returns>
        public new EfzCommand CreateCommand()
        {
            return new EfzCommand(this);
        }

        /// <summary>
        /// Forwards to the local CreateCommand() function
        /// </summary>
        /// <returns></returns>
        protected override DbCommand CreateDbCommand()
        {
            return CreateCommand();
        }

        /// <summary>
        /// Returns the filename without extension or path
        /// </summary>
        public override string DataSource
        {
            get
            {
                return _connectionOptions.DataSource;
            }
        }

        /// <summary>
        /// Returns an empty string
        /// </summary>
        public override string Database
        {
            get
            {
                return _connectionOptions.Database;
            }
        }     
            

        /// <summary>
        /// Opens the connection using the parameters found in the <see cref="ConnectionString">ConnectionString</see>
        /// </summary>
        public override void Open()
        {
            if (_connectionState != ConnectionState.Closed)
                throw new InvalidOperationException("Conection already open.");

            if (_connectionOptions.Enlist && System.Transactions.Transaction.Current == null)
            {
                throw new InvalidOperationException("No active Transaction to enlist.");
            }

            Close();
            try
            {

                if (_connectionOptions.Pooling)
                {
                    InnerConnection = EfzConnectionPool.Remove(_connectionString, _connectionOptions.MaxPoolSize,
                        _connectionOptions.MinPoolSize, out _poolVersion);
                    if (InnerConnection != null && InnerConnection.SessionProxy.isClosed())
                        InnerConnection = null;
                }

                if (InnerConnection == null)
                {
                    InnerConnection = new EfzConnectionProxy(_connectionOptions, this);
                    InnerConnection.Open();
                }
                else
                {
                    InnerConnection.Owner = this;
                }

                

                Version++;

                ConnectionState oldstate = _connectionState;
                _connectionState = ConnectionState.Open;

                if (Transactions.Transaction.Current != null && _connectionOptions.Enlist)
                    EnlistTransaction(Transactions.Transaction.Current);

                _connectionState = oldstate;
                OnStateChange(ConnectionState.Open);

            }
            catch (EfzException)
            {
                Close();
                throw;
            }

        }

        /// <summary>
        /// Gets/sets the default command timeout for newly-created commands.  This is especially useful for 
        /// commands used internally such as inside a EffiProzDBTransaction, where setting the timeout is not possible.
        /// This can also be set in the ConnectionString with "Default Timeout"
        /// </summary>
        public int DefaultCommandTimeout
        {
            get { return _connectionOptions.CommandTimeout; }
           
        }

        /// <summary>
        /// Returns the version of the underlying EffiProz database engine
        /// </summary>
        public override string ServerVersion
        {
            get
            {
                if (State != ConnectionState.Open)
                    throw new InvalidOperationException("Connection is not open.");

                return EffiProzVersion;         
            }
        }

        /// <summary>
        /// Returns the version of the underlying EffiProz database engine
        /// </summary>
        public static string EffiProzVersion
        {
            get { return "EffiProz 0.1.0 ALPA"; }
        }

        /// <summary>
        /// Returns the state of the connection.
        /// </summary>
        public override ConnectionState State
        {
            get
            {
                return _connectionState;
            }
        }    
             
    
        /// <returns>A DataTable of the MetaDataCollections schema</returns>
        public override DataTable GetSchema()
        {
            return GetSchema("MetaDataCollections", null);
        }

        /// <summary>
        /// Returns schema information of the specified collection
        /// </summary>
        /// <param name="collectionName">The schema collection to retrieve</param>
        /// <returns>A DataTable of the specified collection</returns>
        public override DataTable GetSchema(string collectionName)
        {
            return GetSchema(collectionName, new string[0]);
        }

        /// <summary>
        /// Retrieves schema information using the specified constraint(s) for the specified collection
        /// </summary>
        /// <param name="collectionName">The collection to retrieve</param>
        /// <param name="restrictionValues">The restrictions to impose</param>
        /// <returns>A DataTable of the specified collection</returns>
        public override DataTable GetSchema(string collectionName, string[] restrictionValues)
        {
            if (_connectionState != ConnectionState.Open)
                throw new InvalidOperationException();

            EfzMetaData md = new EfzMetaData(this);

            return md.GetMetaData(collectionName, restrictionValues);
        
        }

        public bool SetAutoCommit(bool enable)
        {
            bool oldEnable = InnerConnection.SessionProxy.isAutoCommit();
            InnerConnection.SessionProxy.setAutoCommit(enable);
            return oldEnable;
        }

        /// <summary>
        /// Change the password (or assign a password) to an open database.
        /// </summary>
        /// <remarks>
        /// No readers or writers may be active for this process.  The database must already be open
        /// and if it already was password protected, the existing password must already have been supplied.
        /// </remarks>
        /// <param name="newPassword">The new password to assign to the database</param>
        public void ChangePassword(string newPassword)
        {
            if (_connectionState != ConnectionState.Open)
                throw new InvalidOperationException("Database must be opened before changing the password.");
                   
            string sql = "SET PASSWORD \""+ newPassword+"\";";

            InnerConnection.ExecuteDirect(sql);
           
        }

        object ICloneable.Clone()
        {
            return new EfzConnection(this.ConnectionString);
        }
          
    }
}
